//Includes
#include <p18cxxx.h>
#include <stdlib.h>
#include "define.h"
#include <math.h>
#include <string.h>
#include <delays.h>
#include "stringconst.h"
 
//Global Variables
 
char recvbuf[I_BUFSIZE]; //All received commands are saved here (both serial and keypress), to be processed later
char transbuf[O_BUFSIZE]; //Output to the serial port is buffered here for the serial_transmit() to send
char dispbuf[O_BUFSIZE]; //Output to the LCD screen is buffered here for the LCD to display
 
volatile char *recvbuf_iPtr = &recvbuf[0];//insertion pointer
volatile char *recvbuf_ePtr = &recvbuf[0];//removal pointer
volatile char transbufi = 0;             // buffer insertion index from string
volatile char transbufr = 0;             // buffer removal index to transmit shift register
 
unsigned int ADSamples[VBUFSIZE]; //Buffer of the N most recent voltage samples (in millivolts)
 
//massConvertLookup[i][j] is converting unit i to unit j
float massConvertLookup[][2] = {{1.0, 0.0352739619},
                               {28.349523125, 1.0}};
/* y = mx + b, where y is the result mass in unit i, x is the voltage,
m is the gradient voltageToMass[i][0], b is the y-intercept voltageToMass[i][1]. */
float voltageToMassEquation[][2] = {{60.98824, -53.51852},
                                     {1700.0,  -1.0}};
//The ounce y-intercept value is a bit inaccurate, but it's round here or later so I don't think it matters in the end
 
unsigned int tareVoltage = 0;    //The tare, as a pure voltage
unsigned int tareMass = 0;
char voltageH = 0;    //The value from ADRESH
char voltageL = 0;  //The value from ADRESL
 
unsigned int sampleInterval = 12500;    //The increment count for the A/D sample rate. = 125 * period in ms.
char newADValue = FALSE;    //Is there a new ADValue?
unsigned char unit = GRAMS;    //The unit currently being used
int mass = 0;    //The current mass, in the current unit
int varMass = 0; //Variance in mass;
int rawMass = 0; // mass for raw weight mode
 
//Strings
unsigned char suffixGrams[] = " grams\r\n"; //the compiler treats initialised strings as const
unsigned char suffixOunces[] = " ounces\r\n"; //If anyone works out a more elegant way of doing the suffixes, do so
unsigned char unitError[] = " <<UNIT ERROR>>\r\n"; //An error message
 
unsigned char romstring[64]; //Space to dump strings when fetching from ROM (stops the memory being eaten by other bits of the program)
 
// Keypad Lookup Table
 
const char keypad_lookuptable[] = {'1','2','3','n','4','5','6','n','7','8','9','n','*','0','#'};
 
extern unsigned char LCDWantsToSend;
 
 
//Prototypes
void low_ISR(void);
void high_ISR(void);
extern void setup(void);
void transmit(unsigned char* message);
void serial_receive(void);
void keypad_receive(void);
 
void store_sample(unsigned int voltage);
unsigned int getVoltage(void);
 
unsigned int getAverage(unsigned int* set, int size);
unsigned int getDeviation(unsigned int* set, int size, unsigned int mean);
int getMass(unsigned int voltage, char massUnit, char applyTare, char forDev);
void int_to_string(int rawNumber, unsigned char* string);
 
void transchar(void);
unsigned char* get_rom_string(const rom unsigned char* txPtr);
 
float convert_unit(float convertMass, char inputUnit, char outputUnit);
void calibrate(unsigned int zeroVoltage, unsigned int secondVoltage, int secondMass, char massUnit);
void setTare(void);
void printInstructions(char mode, char func);
void generateLine1(char key, unsigned char* function);
void generateLine2(char key, unsigned char* function);
 
extern void checkState (void);
 
extern unsigned char isBusy(void);
extern void sendNext(void);
 
#pragma config WDT = OFF //Stops the watchdog timer from restting the PIC
 
//Interrupt Jump Vectors
#pragma code high_vector = 0x0008
 
void high_interrupt(void)
{
    _asm goto high_ISR _endasm
}
 
#pragma code low_vector = 0x0018
 
void low_interrupt(void)
{
    _asm goto low_ISR _endasm
}
 
#pragma code
/*The actual code begins here!*/
 
/**
 * High priority interrupt service routine (ISR).
 * Central functions triggered here are inputs by Serial Receive and Keypad.
 * /
 
void high_ISR(void)
{
    char rcdebug = 0;
    static unsigned char LVDstring[] = "LOW VOLTAGE DETECTED";
    /*All interrupts will check the mask (PIE) before checking the request (PIR)*/
    //Disable ALL interrupts
    INTCONbits.GIEH = 0;
 
    //Check for receive int
    if(PIE1bits.RCIE == 1 && PIR1bits.RCIF == 1)
    {
        serial_receive();
    }
 
    //Check for external keypress
    if(INTCON3bits.INT1IF ==1)
    {
        keypad_receive();
        INTCON3bits.INT1IF = 0;
    }
 
    if(PIE2bits.LVDIE == 1 && PIR2bits.LVDIF == 1)
    {
        transmit(LVDstring);
    }
 
    INTCONbits.GIEH = 1;
}
 
 
/**
 * Low priority interrupt service routine (ISR).
 * Central functions triggered here are Serial Transmit and receiving A/D value.
 * /
void low_ISR(void)
{
    /*All interrupts will check the mask (PIE)
    before checking the request (PIR)*/
    //Disable ONLY LOW interrupts
    INTCONbits.GIEL = 0;
 
    //Check for transmit empty int
    if(PIE1bits.TXIE == 1 && PIR1bits.TXIF == 1)
    {
        transchar();
    }
 
    if(PIR1bits.ADIF == 1 && PIE1bits.ADIE == 1) //If A/D interrupt enabled and triggered
    {
        voltageH = ADRESH;
        voltageL = ADRESL;
        newADValue = TRUE;
        PIR1bits.ADIF = 0;
    }
 
    INTCONbits.GIEL = 1;
}
 
/**
 * Main function loop.
 * Each loop it will call the checkState() function to determine the next action to be taken by the program.
 * It will also test to see if the A/D has collected a new voltage value.
 * If so, it will store this value into the A/D buffer, and increment the buffer counter j.
 * When j reaches the size of the buffer, the system will call getMass and getDeviation to calculate
 * the mass and variance values based on the current A/D buffer.
 * The main loop also sends characters to the LCD if it is ready and there is data waiting to be sent.
 * /
 
void main(void)
{
    unsigned int voltage = 0;
    unsigned int aveVoltage = 0;
    unsigned int varianceVolt = 0;
    int varianceMass = 0;
    unsigned char i = 0, j = 0;
 
    //Clear transmit buffer at start of program
    for(i = 0; i < O_BUFSIZE; i++)
        transbuf[i] = 0;
 
    setup(); //Initialise all registers
 
 
    while(1)
    {
 
    if(newADValue)
       {
            newADValue = FALSE;
            voltage = getVoltage();
            store_sample(voltage);
 
            j++;
            if (j == VBUFSIZE)
            {
                j = 0;
                aveVoltage = getAverage(ADSamples, VBUFSIZE);
                mass = getMass(aveVoltage, unit, TRUE, FALSE); //apply tare
 
                varianceVolt = getDeviation(ADSamples, VBUFSIZE, aveVoltage);
                varMass = getMass(varianceVolt, unit, FALSE, TRUE);
            }
        }
 
    checkState();
    if(!isBusy() && LCDWantsToSend)
    {
        sendNext();
    }
    }
 
}
 
 
 
/**
 * Gets the average of an array of values of pre-defined size.
 * The function adds all the values in the array then divides by the size.
 * @param set The array of unsigned integers to average. It need not be null-terminated in any way.
 * @param size The number of integers in the array.
 * @return mean Returns the mean value of the array as an unsigned integer.
 */
 
unsigned int getAverage(unsigned int* set, int size)
{
    int i = 0;
    long int total = 0;
    int mean = 0;
    for(i = 0; i < size; i++)
    {
        total += set[i];
    }
 
    mean = total/size;
 
    return mean;
}
 
 
/**
 * Gets the variance of an array of values of pre-defined size, with a pre-calculated mean.
 * The function used is a simple finite-population variance equation:
 * variance = sqrt( 1/size * sum of (xi - mean)^2 ) for each sample xi
 * @param set The array of unsigned integers to calculate variance from. It need not be null-terminated in any way.
 * @param size The number of integers in the array.
 * @param mean The average value of the numbers in the array.
 * @return variance Returns the variance of the mumbers in the array as an unsigned integer.
 */
 
unsigned int getVariance(unsigned int* set, int size, unsigned int mean)
{
    // variance = sqrt( 1/size * sum of (xi - mean)^2 )
    int i = 0;
    float totalf = 0.0, incf = 0.0, sizef = 0.0, meanf = 0.0, valf = 0.0, subf = 0.0, subf2 = 0.0;
 
    unsigned int variance = 0;
 
    meanf = (float)mean;
    sizef = (float)size;
 
    for(i = 0; i < size; i++)
    {
        valf = (float)set[i];
        subf = valf - meanf;
        subf2 = subf * subf;
        incf = subf2/sizef;
        if (incf < 9999.0) // Exclude values that are clearly incorrect
          totalf += incf;
    }
 
    variance = (unsigned int)totalf;
 
    return variance;
}
 
/**
 * Stores values from the A/D converter in a small circular buffer (VINSERT = 8 values long)
 * No processing is done. It is up to the getMean() and getVariance() functions to interperet the data
 * Values are stored in the global array "ADSamples[]".
 * @param voltage The value to be stored. The oldest values will be dropped to make way for new ones.
 * @return void.
 */
void store_sample(unsigned int voltage)
{
    static char vinsert = 0;
 
    ADSamples[vinsert] = voltage;
 
    /*advance the position in the buffer, loop around at the end*/
    vinsert++;
    if(vinsert >= VBUFSIZE)
    {
      vinsert = 0;
    }
}
 
/**
 * A function to convert a signed integer to a string of ascii numerical characters.
 * By progressively dividing the integer passed by 10s, 100s, 1000s etc, the value of
 * each digit in the number can be determined. This is then used as an index to look
 * up the value of the appropriate character. Each character is placed into the output
 * string in the correct location so that it represents a decimal number.
 * This function also checks for negative values, and affixes a '-' sign if appropriate
 * @param rawNumber A signed integer less than +- ten thousand..
 * @param string The return string, which is modified by this function. It will be overwritten and null terminated.
 * @return void.
 */
void int_to_string(int rawNumber, unsigned char* string)
{
    char digits[11] = "0123456789"; //the 11th character is /0
    unsigned int thousands = 0, hundreds = 0, tens = 0, ones = 0;
    int t1 = 0, t2 = 0, t3 = 0;
    int number;
 
    if(rawNumber < 0)
    {
        number = 0 - rawNumber;
        string[0] = '-';
    }
    else
    {
        number = (int)rawNumber;
        string[0] = ' ';
    }
 
    thousands = number / 1000;
    hundreds = (number - 1000*thousands)/100;
    tens = (number - 1000*thousands - 100*hundreds)/10;
    ones = (number - 1000*thousands - 100*hundreds - 10*tens);
 
    string[1] = digits[thousands];
    string[2] = digits[hundreds];
    string[3] = digits[tens];
    string[4] = digits[ones];
    if(thousands == 0)
    {
        string[1] = ' ';
        if(hundreds == 0)
        {
            string[2] = ' ';
            if(tens == 0)
                string[3] = ' ';
        }
    }
    string[5] = '\0';
}
 
 
/**
 * Called when the serial receive interrupt is triggered, this will read the RCREG
 * and then copy the character onto the input stream (recvbuf). This function will
 * modify the insertion and extraction pointers recvbuf_iPtr and recvbuf_ePtr.
 * @post The character from RCREG will be copied to recvbuf, and RCREG is cleared
 */
void serial_receive(void)
{
    PIR1bits.RCIF = 0;    // Clear RC flag
 
    if (RCREG == 0x0D | RCREG == 0x0A)
    {
    // Place special handling for 'enter' here.
    }
 
    *recvbuf_iPtr = RCREG;    // Place recieved char into insertion location
 
    recvbuf_iPtr = recvbuf_iPtr++;                // Increment insertion pointer.
    if (recvbuf_iPtr > recvbuf + I_BUFSIZE)
        {
        recvbuf_iPtr = &recvbuf[0];
        }
}
 
/**
 * Converts the input receive in PORTB from the MM74C922 encoder chip
 * into a decimal number by right shifting and bit masking
 * Uses #define LOW_NIBBLE for bit masking
 * Uses the const char keypad_lookup table to receieve the value of
 * which button entered by the user
 * Usage example:
 * User presses the '1' key = b00000000( in binary) = 0( in decimal)= '1' in lookup table
 * @param temp Temporary value used to receieve PORTB values
 * @param key_pressed A string value representing the button pressed
 * @param recvbuf Used to store the string value
 * @return void
 */
 
void keypad_receive(void)
{
    char temp;
    char key_pressed;
 
    temp = (PORTB>>2)&LOW_NIBBLE;                 // 2x Right shifting and bit masking
    key_pressed = keypad_lookuptable[temp]        // Getting string valur from table
    *recvbuf_iPtr = key_pressed;
    recvbuf_iPtr = recvbuf_iPtr++;                // Increment insertion pointer.
    if (recvbuf_iPtr > recvbuf + I_BUFSIZE)
        {
        recvbuf_iPtr = &recvbuf[0];
        }
 
}
 
/**
 * Some strings are stored in the ROM. This function
 * will fetch the string from ROM, place it in RAM,
 * and return a pointer to the start of the RAM string.
 * @param txPtr This is a pointer to the address of the start of the string in ROM. Do not pass RAM strings to this!
 * @return romstring romstring is a block of (RAM) memory set aside to dump ROM strings into when they are being fetched.
 */
unsigned char* get_rom_string(const rom unsigned char* txPtr)
{
    strcpypgm2ram(romstring,(far rom unsigned char*)txPtr);
    return romstring;
}
 
 
/**
 * Copies a string into the transmit buffer, for later sending.
 * @param trans_string The null-terminated string of ASCII characters for transmission.
 */
 
void transmit(unsigned char* trans_string)
// Transmit function, writes string into buffer and starts sending
{
    char i = 0;
    Delay1KTCYx(10);
    while(trans_string[i] != '\0')        // until null termination of string reached
    {
        transbuf[transbufi] = trans_string[i];        // copy string into transmit buffer
        transbufi++;
        if(transbufi >= O_BUFSIZE)// catch insertion index overflow
        {
            transbufi = 0;
        }
        i++;
    }
    PIE1bits.TXIE = 1;                 // enable transmit interrupt
    //Because the transmit interrupt is enabled, the output string will start being sent immediately
}
 
/**
 * Copies a character into the transmit shift register for transmission.
 * There are no inputs as it accesses the global transmit buffer.
 */
 
void transchar(void)
// Transmit function, sends next char to TXREG as long as transmission buffer has more material to write
{
    TXREG = transbuf[transbufr];         // load character into transmit shift register
 
    transbufr++;                // increment removal index
    if(transbufr >= O_BUFSIZE)
    {
        transbufr = 0;         // catch removal index overflow
    }
 
    if (transbufr  == transbufi)         // if removal index catches insertion index, end of transmission
    {
        PIE1bits.TXIE = 0;         // disable transmit interrupt
    }
}
 
/**
 * Clears the HyperTerminal screen by transmitting the screen feed control character.
 */
void clearScreen(void)
{
    transmit(get_rom_string(_cls));
}
 
/**
 * This is a very simple function that will make sure only allowable
 * characters get passed to the state machine. Only the chars representing:
 * WEIGH, COUNT, TARE, CALIBRATE, STATS, RAW and SAMPLES are valid.
 * @param iChar A character which is to be checked for validity.
 * @return oChar Valid characters are passed through. Invalid characters return 0.
unsigned char parseFunc(unsigned char iChar)
{
    if(iChar == WEIGH|iChar == COUNT|iChar == TARE|iChar == CALIBRATE|iChar == STATS|iChar == RAW|iChar == SAMPLES)
    {
        return iChar;
    }
    else
    {
        return 0;
    }
}
 
 
/**
 * Similar to parseFunc, but for the active mode. See parseFunc documentation also.
 * Ensures only characters valid for LOCAL, REMOTE or FACTORY modes are passed to the state machine.
 * @param iChar A character which is to be checked for validity.
 * @return oChar Valid characters are passed through. Invalid characters return 0.
 */
unsigned char parseMode(unsigned char iChar)
{
    if(iChar == LOCAL|iChar == REMOTE|iChar == FACTORY)
    {
        return iChar;
    }
    else
    {
        return 0;
    }
}
 
 
/**
 * Prints the user menu to HyperTerminal using serial transmit.
 * The menu changes depending on the active mode (USER vs. FACTORY) and function, since different options become available.
 * @param mode Either LOCAL or REMOTE, which will indicate where the instructions should be streamed to
 * @param func The function for which the instructions should be displayed.
 */
 
void printInstructions(char mode, char func)
{
    unsigned char asterisks[] = "*****";
 
 
 
    transmit(get_rom_string(_outro));
    transmit(asterisks);
    transmit(get_rom_string(_outro));
 
    transmit(get_rom_string(_intro));
    switch (mode)
    {
        case LOCAL:
            transmit(get_rom_string(_ul));
            break;
 
        case REMOTE:
            transmit(get_rom_string(_ur));
            break;
 
        case FACTORY:
            transmit(get_rom_string(_fy));
            break;
 
    }
    transmit(get_rom_string(_cs));
    switch (func)
    {
        case WEIGH:
        transmit(get_rom_string(_wh));
            break;
 
        case COUNT:
        transmit(get_rom_string(_ct));
            break;
 
        case STATS:
        transmit(get_rom_string(_ss));
            break;
 
        case RAW:
        transmit(get_rom_string(_rw));
            break;
 
        case SAMPLES:
        transmit(get_rom_string(_sr));;
            break;
 
        case CALIBRATE:
        transmit(get_rom_string(_ce));
            break;
 
    }
    transmit(get_rom_string(_fs));
 
    transmit(get_rom_string(_outro));
 
    switch (mode)
    {
        case LOCAL:
            generateLine1(REMOTE,_ur);
            break;
 
        case REMOTE:
            generateLine1(LOCAL,_ul);
            break;
    }
 
    if(func != WEIGH)
    {
        generateLine1(WEIGH,_wh);
    }
 
    if(func != COUNT)
    {
        generateLine1(COUNT,_ct);
    }
 
    transmit(get_rom_string(_outro));
    generateLine2(TARE,_te);
    generateLine2(CHANGEUNIT,_ts);
 
    if (mode == FACTORY)
    {
        transmit(get_rom_string(_outro));
        transmit(get_rom_string(_fac1));
        generateLine1(STATS,_ss);
        generateLine1(RAW,_rw);
        generateLine1(SAMPLES,_sr);
        generateLine1(CALIBRATE,_ce);
        transmit(get_rom_string(_outro));
    }
 
    transmit(asterisks);
    transmit(get_rom_string(_outro));
}
 
/**
 * Generates a menu instruction line for changing the active function. The line is transmitted via serial.
 * @param key A character corresponding to the key to be pressed to enter the desired function.
 * @param function A string with the name of the function referred to, such as "Count" or "Weigh".
 * Constant text elements "Press __ to enter __ mode." are copied from program memory with get_rom_string.
 * Usage example: generateLine1('w', "Weigh") will return "Press w to enter Weigh mode."
 */
void generateLine1(char key, unsigned char* function)
{
    unsigned char keystr[2]; // assemble null-terminated string from character passed to function
    keystr[0] = key;
    keystr[1] = '\0';
 
    transmit(get_rom_string(_11));
    transmit(keystr);
    transmit(get_rom_string(_12));
    transmit(get_rom_string(_13));
    transmit(get_rom_string(function));
    transmit(get_rom_string(_14));
    transmit(get_rom_string(_outro));
}
 
 
/**
 * Generates a menu instruction line for performing supplementary tasks (tare, change unit type). The line is transmitted via serial.
 * @param key A character corresponding to the key to be pressed to perform the task.
 * @param function A string with the name of the task referred to, such as "Toggle Units" or "Tare".
 * Constant text elements "Press __ to __." are copied from program memory with get_rom_string.
 * Usage example: generateLine2('u', "Toggle Units") will return "Press u to Toggle Units."
 */
void generateLine2(char key, unsigned char* function)
{
    unsigned char keystr[2]; // assemble null-terminated string from character passed to function
    keystr[0] = key;
    keystr[1] = '\0';
 
    transmit(get_rom_string(_11));
    transmit(keystr);
    transmit(get_rom_string(_12));
    transmit(get_rom_string(function));
    transmit(get_rom_string(_fs));
    transmit(get_rom_string(_outro));
}
 
/**
 * Returns an unsigned 16 bit integer, containing 10 bits of A/D result. The 6 most
 * significant bits will be 0.
 * Precondition: The A/D Converter is on and initialised.
 * Precondition: The special event trigger has enabled the A/D converter, and then an interrupt
 * has moved the ADRESH and ADRESL values to voltageH and voltageL, respectively.
 * @return 10 bit A/D result
 */
unsigned int getVoltage(void)
{
    unsigned int result = 0;
    unsigned int lhold = 0;
 
    result = voltageH * 256;
    lhold = voltageL & 0x00FF;
    result = result + lhold;
 
    return result;                    //Result is 10 bits
}
/**
 * Gets the current mass, as ounces or grams, as specified by the parameters.
 * It uses either the predefined y-intercept and gradient, or one set by using
 * the calibrate function.
 * @param voltage The voltage corresponding to the desired mass.
 * @param massUnit The unit of mass to use, either GRAMS or OUNCES (#defined above).
 * @param applyTare A boolean value. A value evaluating to true will mean that the mass is adjusted for tare.
 * @param forVar A boolean value. A value evaluating to true will mean that no offset is applied, since this is a variance calculation.
 */
int getMass(unsigned int voltage, char massUnit, char applyTare, char forVar)
{
    //y = mx + b to get base result
    float mx = 0.0;
    float b = 0.0;
    int resultmass = 0;
 
    mx = voltage * 64.0 / voltageToMassEquation[massUnit][0];
    if (forDev == TRUE) // this is a standard deviation conversion and no offset is required
        b = 0.0;
    else if (forDev == FALSE)
        b = voltageToMassEquation[massUnit][1];
 
     //mass = voltageToMassEquation[massUnit][0] * voltage + voltageToMassEquation[massUnit][1];
    resultmass = (int)(mx + 0.5) + (int)(b + 0.5);
 
//    rawMass = resultmass;
 
    if(applyTare)
        resultmass = resultmass - tareMass;
 
    return resultmass;
}
 
/**
 * Sets the tare value.
 * Sets the global value tareMass to the current value of global mass.
 * This is subtracted within the getMass function if a tared value is requested by the user.
 */
void setTare(void)
{
    tareMass += mass; // May give error if unit type switched after performing tare, but can be fixed by taring again.
}
 
 
 
/**
 * Converts a mass from one unit to another.
 * Uses the #define conventions of masses, that is, GRAMS = 0,
 * and OUNCES = 1.
 * Usage example:
 * ounceValue = convertUnit(gramValue, GRAMS, OUNCES);
 * @param convertMass The input mass
 * @param inputUnit A numerical char representing the input mass type
 * @param outputUnit A numerical char represeting the output mass type
 * @return float The converted mass.
 */
float convert_unit(float convertMass, char inputUnit, char outputUnit)
{
    convertMass = convertMass * massConvertLookup[inputUnit][outputUnit];
    return convertMass;
}
 
/**
 * Use two points, and simple maths, to work out a calibration line.
 * If the input values are invalid, for any reason, the function will end.
 * @param zeroVoltage The voltage at a zero mass point
 * @param secondVoltage The voltage at some positive non-zero mass point.
 * @param secondMass The positive non-zero mass point. The value is in either grams or ounces
 * @param massUnit Specifies what unit secondMass is in, either GRAMS or OUNCES.
 */
void calibrate(unsigned int zeroVoltage, unsigned int secondVoltage, int secondMass, char massUnit)
{
    //Algorithm:
    //m = v * 64 / x - y
    //For v1, 0 and v2, m:
    //y = v1 * 64 / x
    //y = v2 * 64 / x - m
    //Thus:
    //v1 * 64 / x = v2 * 64 / x - m
    //Therefore:
    //x = 64 * (v2 - v1) / m
    //y = - v1 * m / (v2 - v1)
    //Also:
    //x(otherUnit) = convert_unit(x, otherUnit, massUnit);    //Note order. Equivalent to division
    //y(otherUnit) = convert_unit(y, massUnit, otherUnit);
 
    float x = 0.0;
    float y = 0.0;
    char otherMassUnit = 1 - massUnit;    //0 or 1
    float voltageDiff = secondVoltage - zeroVoltage;
    if(voltageDiff == 0)    //Error handling
        return;
    x = 64 * voltageDiff / secondMass;
    y = zeroVoltage * secondMass / voltageDiff;
    y = - y;
 
    voltageToMassEquation[massUnit][0] = x;
    voltageToMassEquation[massUnit][1] = y;
 
    voltageToMassEquation[otherMassUnit][0] = convert_unit(x, otherMassUnit, massUnit);
    voltageToMassEquation[otherMassUnit][1] = convert_unit(y, massUnit, otherMassUnit);
}